using System;
using System.Net;
using System.Net.Sockets;
using System.Collections;
using SMS.SMPPClient;
using SMS.SMPPClient.PDU;
using SMS.SMPPClient.PDU.Outgoing;
using SMS.SMPPClient.PDU.Incoming;
using SMS.SMPPClient.Util;

namespace SMS.SMPPClient
{
    public delegate void ConnectDelegate(object sender, SmppSockEventArgs e);
    public delegate void DisconnectDelegate(object sender, SmppSockEventArgs e);
    public delegate void SendDelegate(object sender, SmppSockEventArgs e);
    public delegate void ReceiveDelegate(object sender, SmppSockEventArgs e);
    public delegate void CloseDelegate(object sender, SmppSockEventArgs e);
    public delegate void SockErrDelegate(object sender, SmppSockEventArgs e);
    public delegate void MessageDelegate(object sender, SmppSockEventArgs e);
    public delegate void SendingDelegate(object sender, SmppSockEventArgs e);
    public delegate void RequestDelegate(object sender, SmppSockEventArgs e);
    /// <summary>
    /// SmppSock
    /// </summary>
    public class SmppSockEventArgs : EventArgs
    {
        public object response;
        public SmppSockEventArgs()
        {
        }
        private byte[] msg = new byte[0];
        public byte[] Msg
        {
            get
            {
                return msg;
            }
            set
            {
                if (value.Length > 0)
                {
                    msg = new byte[value.Length];
                    value.CopyTo(msg, 0);
                }
            }
        }
    }

    /// <summary>
    /// SmppSock
    /// </summary>
    public class SmppSock
    {
        public class StateObject
        {
            public Socket workSocket = null;
            public const int BufferSize = 256;
            public byte[] buffer = new byte[BufferSize];
        }
        public Socket Soc;
        // ArrayList
        public ArrayList response = new ArrayList();

        // Declare
        public event ConnectDelegate OnConnected;
        public event DisconnectDelegate OnDisconnected;
        public event SendDelegate OnReadySend;
        public event ReceiveDelegate OnReadyReceive;
        public event CloseDelegate OnConnectClose;
        public event SockErrDelegate OnSocketError;
        public event MessageDelegate OnSockMessage;
        public event RequestDelegate OnSockRequest;


        // SmppSock
        private SmppSockEventArgs sockArgs = new SmppSockEventArgs();

        // Status Var
        private bool lSocketClosed;

        public enum XY { SMPP, CMPP, SGIP, NOKIA }
        public XY xy = XY.SMPP;

        public SmppSock(XY xy)
        {
            this.xy = xy;
            Soc = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        }
        /// <summary>
        /// Asyn connection
        /// </summary>
        /// <param name="remoteEP">IP Address</param>
        public void AsyConnectTCIP(EndPoint remoteEP)
        {
            AsyncCallback beginConnectCallback = new AsyncCallback(ConnectCallBack);
            Soc.BeginConnect(remoteEP, beginConnectCallback, Soc);
        }
        /// <summary>
        /// BeginConnnect
        /// </summary>
        /// <param name="ar">IAsyncResult</param>
        private void ConnectCallBack(IAsyncResult ar)
        {
            try
            {
                Socket soc = (Socket)ar.AsyncState;
                soc.EndConnect(ar);
                if (soc.Connected)
                {
                    lSocketClosed = false;
                    if (OnConnected != null)
                        OnConnected(this, sockArgs);// OnConnected
                }
                else
                {
                    if (OnDisconnected != null)
                        OnDisconnected(this, sockArgs);// OnDisconnected
                }
            }
            catch (Exception e)
            {
                RaiseSockErrEvent("Connect : " + e.Message);
                CloseSocket();
            }
        }
        /// <summary>
        /// Socket
        /// </summary>
        public void CloseSocket()
        {
            //if the sent file still opening Close it.
            //cause the connection is down.
            if (lSocketClosed)
                return;

            try
            {
                lSocketClosed = true;
                if (Soc != null)
                {
                    if (Soc.Connected)
                    {
                        Soc.Shutdown(SocketShutdown.Both);
                        Soc.Close();
                    }
                }

            }
            catch (ObjectDisposedException err)
            {
                RaiseSockErrEvent("Close : " + err.Message);
            }

            if (OnConnectClose != null)
            {
                OnConnectClose(this, sockArgs);
            }

        }
        /// <summary>
        /// Raise Sock Event
        /// </summary>
        /// <param name="errMsg">string</param>
        private void RaiseSockErrEvent(string errMsg)
        {
            sockArgs.Msg = System.Text.Encoding.Default.GetBytes(errMsg);
            if (OnSocketError != null)
                OnSocketError(this, sockArgs);
        }

        public void SubmitMsg(string sourceAddress, string destinationAddress, bool ucs2, string msg)
        {
            switch (xy)
            {
                case XY.SMPP:
                    {
                        SMS.SMPPClient.PDU.Outgoing.SMPPSubmitSM submit =
                            new SMS.SMPPClient.PDU.Outgoing.SMPPSubmitSM("0", PDU.TONType.International, PDU.NPIType.ISDN,
                            sourceAddress, PDU.TONType.International, PDU.NPIType.ISDN,
                            destinationAddress, 0, 0, PDU.PriorityType.Lowest, null,
                            null, PDU.RegisteredDeliveryType.None, false,
                            0, (ucs2) ? PDU.DataCodingType.ucs2 : PDU.DataCodingType.SMSCDefault,
                            (ucs2) ? (System.Text.Encoding.BigEndianUnicode.GetBytes(msg)) : System.Text.Encoding.ASCII.GetBytes(msg));
                        Send(submit);
                    } break;
                case XY.SGIP:
                    {
                        RoaminSGIP.PDU.Outgoing.SubmitSM bind =
                            new RoaminSGIP.PDU.Outgoing.SubmitSM(sourceAddress, destinationAddress, (ucs2) ? 8 : 15, (ucs2) ? (System.Text.Encoding.BigEndianUnicode.GetBytes(msg)) : System.Text.Encoding.Default.GetBytes(msg));
                        Send(bind);
                    } break;
            }
        }

        public void EnquireLink()
        {
            SMS.SMPPClient.PDU.Outgoing.SMPPEnquireLink enquireLink =
                new SMS.SMPPClient.PDU.Outgoing.SMPPEnquireLink();
            Send(enquireLink);
        }

        public void SendBind(string user, string pwd)
        {
            switch (xy)
            {
                case XY.SMPP:
                    {
                        SMS.SMPPClient.PDU.Outgoing.SMPPBind bind =
                            new SMS.SMPPClient.PDU.Outgoing.SMPPBind(user, pwd,
                            "", SMS.SMPPClient.PDU.Outgoing.SMPPBind.SMPPVersionType.Version3_4,
                            SMS.SMPPClient.PDU.PDU.TONType.Unknown,
                            SMS.SMPPClient.PDU.PDU.NPIType.Unknown,
                            "", SMS.SMPPClient.PDU.Outgoing.SMPPBind.BindingType.BindAsTransmitter);
                        Send(bind);
                    } break;
                case XY.SGIP:
                    {
                        RoaminSGIP.PDU.Outgoing.SGIPBind bind =
                            new RoaminSGIP.PDU.Outgoing.SGIPBind(user, pwd, 382032);
                        Send(bind);
                    } break;
            }
        }
        public void Send(byte[] buf)
        {
            if (Soc != null && Soc.Connected)
            {
                int sendLen = buf.Length;
                int pFinish = 0;
                while (pFinish < sendLen)
                {
                    int len = Soc.Send(buf, pFinish, sendLen - pFinish, SocketFlags.None);
                    if (len > 0)
                    {
                        pFinish += len;
                    }
                    else
                    {
                        RaiseSockErrEvent("Bind user Fail");
                        CloseSocket();
                        return;
                    }
                }
                if (OnReadySend != null)
                {
                    sockArgs.Msg = buf;
                    OnReadySend(this, sockArgs);
                }
            }
        }
        public void Send(PDU.Outgoing.SMPPRequest request)
        {
            Send(request.GetMSBHexEncoding());
        }

        public void Send(RoaminSGIP.PDU.Outgoing.SGIPRequest request)
        {
            Send(request.GetMSBHexEncoding());
        }
        /// <summary>
        /// Receive Data
        /// </summary>
        public void ReceiveData()
        {
            try
            {
                StateObject state = new StateObject();
                state.workSocket = Soc;
                Soc.BeginReceive(state.buffer, 0, StateObject.BufferSize, SocketFlags.None,
                    new AsyncCallback(ReceiveCallback), state);
            }
            catch (Exception e)
            {
                RaiseSockErrEvent("Receive : " + e.Message);
                CloseSocket();
            }
        }

        /// <summary>
        /// ReceiveData Callback
        /// </summary>
        /// <param name="ar">IAsyncResult</param>
        private void ReceiveCallback(IAsyncResult ar)
        {
            try
            {
                StateObject state = (StateObject)ar.AsyncState;
                Socket soc = state.workSocket;
                int byteRead = soc.EndReceive(ar);
                if (byteRead > 0)
                {
                    for (int i = 0; i < byteRead; i++) response.Add(state.buffer[i]);

                    CheckCommand();

                    if (OnReadyReceive != null)
                        OnReadyReceive(this, sockArgs);

                    soc.BeginReceive(state.buffer, 0, StateObject.BufferSize, SocketFlags.None,
                        new AsyncCallback(ReceiveCallback), state);
                }
                else
                    CloseSocket();

            }
            catch (Exception e)
            {
                RaiseSockErrEvent("Receive : " + e.Message);
                CloseSocket();
            }
        }

        private void CheckCommand()
        {
            response.TrimToSize();
            byte[] buf = new byte[4];
            response.CopyTo(0, buf, 0, 4);

            System.UInt32 len = System.BitConverter.ToUInt32(buf, 0);
            len = UnsignedNumConverter.SwapByteOrdering(len);
            if (response.Count >= len)
            {
                buf = new byte[len];
                response.CopyTo(0, buf, 0, (int)len);

                response.RemoveRange(0, (int)len);
                response.TrimToSize();

                sockArgs.Msg = buf;

                uint command = UnsignedNumConverter.SwapByteOrdering(System.BitConverter.ToUInt32(buf, 4));
                if (command < 0x80000000)
                {
                    if (OnSockRequest != null)
                        OnSockRequest(this, sockArgs);
                }
                else
                {
                    switch (xy)
                    {
                        case XY.SMPP:
                            {
                                SMS.SMPPClient.Util.PDUFactory pdu = new SMS.SMPPClient.Util.PDUFactory();
                                sockArgs.response = (SMS.SMPPClient.PDU.Incoming.SMPPResponse)pdu.GetPDUQueue(buf).Dequeue();

                                if (sockArgs.response != null)
                                {
                                    if (OnSockMessage != null)
                                        OnSockMessage(this, sockArgs);
                                }
                                else
                                {
                                    RaiseSockErrEvent("SMPPResponse : Error");
                                    CloseSocket();
                                }
                            } break;
                        case XY.SGIP:
                            {
                                sockArgs.response = RoaminSGIP.PDU.Incoming.SGIPResponse.GetPDU(buf);
                                if (OnSockMessage != null)
                                    OnSockMessage(this, sockArgs);
                            } break;
                    }
                }
            }
        }
    }
}
